package net.ib.mtalk.network.task;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.util.UUID;

import net.ib.mntalk.protocol.Common;
import net.ib.mntalk.protocol.Common.FileToken;
import net.ib.mntalk.protocol.Common.UUIDMsg;
import net.ib.mntalk.protocol.Common.UserMessage;
import net.ib.mntalk.protocol.File.UploadReq;
import net.ib.mntalk.protocol.File.UploadRes;
import net.ib.mntalk.protocol.File.ValidateUploadReq;
import net.ib.mntalk.protocol.File.ValidateUploadRes;
import net.ib.mntalk.protocol.Session.AcquireFileTokenReq;
import net.ib.mntalk.protocol.Session.AcquireFileTokenRes;
import net.ib.mntalk.protocol.Session.SendMessageReq;
import net.ib.mntalk.protocol.client.Database;
import net.ib.mntalk.protocol.client.Database.FileTransferInfo;
import net.ib.mtalk.constant.NetworkConstant.FileServer;
import net.ib.mtalk.database.helper.ChatDatabaseHelper;
import net.ib.mtalk.database.helper.InboxDatabaseHelper;
import net.ib.mtalk.global.GlobalPreferences;
import net.ib.mtalk.network.FileClient;
import net.ib.mtalk.network.MethodId;
import net.ib.mtalk.network.NetResultReceiver;
import net.ib.mtalk.network.TcpClient;
import net.ib.mtalk.network.TcpEnvelope;
import net.ib.mtalk.util.GpbSupport;
import net.ib.mtalk.util.MTalkLogWriter;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.provider.MediaStore;
import android.util.Log;

import com.google.protobuf.micro.ByteStringMicro;
import com.google.protobuf.micro.MessageMicro;

/**
 * 파일을 쪼개서 보내준다.
 *  
 * @author Arngard
 *
 */
public class FileUploadTask extends AsyncNetTask implements NetResultReceiver.Receiver {

	/* 소스 매커니즘에 필요한 자원 */
	/** AsyncNetTask에서 콜백 구조는 활용하지 않고, 스레드 자원만 빌려 쓴다. 따라서 자체 콜백이 있어야 함. */
	protected NetResultReceiver mCallback;
	
	/** 대상이 되는 뷰의 정보 */
	private UUID mClientViewUuid;

	/* 메시지의 candidate key */
	/** 서버로부터 할당된 messageUuid */
	protected UUIDMsg mMessageUuid;
	/** 서버로부터 messageUuid 할당이 있기 전 메시지 row를 확인하기 위한 ID */
	protected UUID mClientMsgUuid;

	/* 관리 정보 */
	/** 전송 상태 정보 */
	protected FileTransferInfo mTransferInfo;

	/**
	 * 새로 객체를 만들고 의미상의 기본값으로 transferInfo를 세팅한다.
	 * @param fileUriString 파일 경로는 초기값이 존재하지 않고 처음부터 반드시 의미있는 값이 있어야 한다.
	 * 만일 이 경로로 읽기 가능한 {@link File} 객체를 생성할 수 없다면 {@link FileUploadTask} 객체의 생성은 실패할 것이다.
	 */
	public static FileTransferInfo newFileTransferInfo(String fileUriString) {
		FileTransferInfo transferInfo = new FileTransferInfo();
		// null 대용 값
		transferInfo.setSliceNo(-1);
		transferInfo.setNumOfSlice(0);
		transferInfo.setSliceSize(FileServer.SLICE_DEFAULT_SIZE);
		// 정해진 초기값
		transferInfo.setState(Database.NONE);
		transferInfo.setWorking(false);
		// 반드시 있어야 하는 정보
		transferInfo.setFileUri(fileUriString);
		return transferInfo;
	}
	
	/**
	 * 비휘발성 저장소에 전송 정보를 저장한다.
	 * DB에 넣고 제 3자는 커서로 진행상황을 감시할 수 있는 형태가 될 것으로 보인다.
	 */
	protected void updateNonvolatileInfo() {
		ChatDatabaseHelper.setFileTransferInfo(mContext, mClientMsgUuid, mTransferInfo);
	}
	
	/**
	 * 비휘발성 저장소로부터 전송 정보를 획득한다.
	 */
	protected void extractNonvolatileInfo() {
		mTransferInfo = ChatDatabaseHelper.getFileTransferInfo(mContext, mClientMsgUuid);
	}
	
	/* DB 쿼리 등을 반복하지 않기 위해, 추출해 낸 부가 정보를 이 변수들에 캐시한다 */
	/** 로컬 파일 정보 */
	private File mFile = null;
	/** 로컬 파일 정보 */
	protected File getFile() {
		if (mFile == null) {
			mFile = new File(mTransferInfo.getFileUri());
		}
		return mFile;
	}
	/** 로컬 파일 정보 */
	private int mFileSize = Integer.MIN_VALUE;
	/** 로컬 파일 정보 */
	protected int getFileSize() {
		if (mFileSize == Integer.MIN_VALUE) {
			mFileSize = (int) getFile().length();
		}
		return mFileSize;
	}
	/** 로컬 파일 정보 */
	private int mFileType = -1;
	/** 로컬 파일 정보 */
	protected int getFileType() {
		return mFileType;
	}
	/**
	 * 로컬 파일 정보들을 세팅.
	 * @param string android.net.Uri.getPath()
	 * @param fileType enum GPB.Common.FileType
	 * @throws SecurityException 로컬 파일에 접근 불가
	 * @throws FileNotFoundException 로컬 파일이 해당 경로에 없음
	 */
	protected void setFileInfo(String fileUriString, int fileType)
	throws SecurityException, FileNotFoundException {
		mTransferInfo.setFileUri(fileUriString);
		mFile = new File(fileUriString);
		mFileSize = (int) mFile.length();	// 파일의 총 길이
		mFileType = fileType;
	}
	public static String extractFilePath(Context context, Uri fileUri) throws FileNotFoundException {
		String filePath = fileUri.getPath();
		File file = new File(filePath);
		// 로컬 파일 유효성 체크
		if (! file.exists() || ! file.canRead()) {
			filePath = getRealPath(context, fileUri);
			file = new File(filePath);
			// 다시 체크
			if (! file.exists() || ! file.canRead()) {
				throw new FileNotFoundException("File not found at '"+file.getAbsolutePath()+"'");
			}
		}
		return filePath;
	}
	public static String getRealPath(Context context, Uri uriPath) {
		String []proj = {MediaStore.Images.Media.DATA};
		Cursor cursor = context.getContentResolver().query(uriPath, proj, null, null, null);
		int index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
		cursor.moveToFirst();
		String path = cursor.getString(index);
		path = path.substring(5);
		return path;
	}
	
	/**
	 * fileUri로 FileTransferInfo를 만들어서 {@link #FileUploadTask(Context, Handler, UUID, UUIDMsg, int, FileTransferInfo, int)
	 * FileUploadTask(context, uiHandler, clientViewUuid, messageUuid, requestId,
	 * <i>newTransferInfo</i>(fileUri.getPath()), fileType)}와 같은 작업을 한다.
	 */
	public FileUploadTask(Context context, Handler uiHandler, UUID clientViewUuid, UUIDMsg messageUuid, UUID clientMsgUuid,
			Uri fileUri, int fileType)
	throws SecurityException, FileNotFoundException {
		this(context, uiHandler, clientViewUuid, messageUuid, clientMsgUuid, newFileTransferInfo(extractFilePath(context, fileUri)), fileType);
	}

	/**
	 * 새 작업을 생성한다. 이 생성이 Exception 없이 성공했다면 대상 로컬 파일의 존재와 읽기 권한도 확인된 것이다.
	 * @param context
	 * @param uiHandler 메시지 전송에 쓸 UI 핸들러
	 * @param clientViewUuid 클라이언트가 자체적으로 정의한 뷰 아이디
	 * @param messageUuid 메시지의 candidate key 구성 요소
	 * @param requestId 메시지의 candidate key 구성 요소
	 * @param transferInfo 전송 정보. 반드시 채울 수 있는 모든 정보를 채워서 주야 한다.
	 * @param fileType enum GPB.Common.FileType
	 * @throws SecurityException 로컬 파일에 접근 불가
	 * @throws FileNotFoundException 로컬 파일이 해당 경로에 없음
	 */
	public FileUploadTask(Context context, Handler uiHandler, UUID clientViewUuid, UUIDMsg messageUuid, UUID clientMsgUuid,
			FileTransferInfo transferInfo, int fileType)
	throws SecurityException, FileNotFoundException {
		super(context, uiHandler);

		mClientViewUuid = clientViewUuid;

		mMessageUuid = messageUuid;
		mClientMsgUuid = clientMsgUuid;

		mTransferInfo = transferInfo;	// Uri는 반드시 들어 있어야 한다.
		
		setFileInfo(mTransferInfo.getFileUri(), fileType);
	}
	
	/**
	 * Task 실행될 때 호출된다.
	 * 이 Task에서는 null을 리턴하여 AsyncNetTask.onPreExecute() 환경을 이용하지 않는다.
	 * 네트워크 요청과 결과는 이 Task내에서 알아서 관리한다.
	 * @see net.ib.mtalk.network.task.SendMessageTask#onPreExecute()
	 */
	@Override
	protected TcpEnvelope onPreExecute() {
		mCallback = new NetResultReceiver(new Handler(), this);
		startWork();
		return null;
	}

	/**
	 * 이 Task는 여러 개의 쿼리를 동반하므로 AsyncNetTask의 내부 콜백 구조를 사용하지 않는다.
	 * 따라서 이 함수는 사용되지 않는다.
	 * @see net.ib.mtalk.network.task.SendMessageTask#onPostExecute(int, java.lang.Object, java.lang.Object)
	 */
	@Override
	protected void onPostExecute(int resultCode, Object sentObject, Object receivedObject) {
		// do nothing
	}

	/**
	 * 네트워크 응답을 적절한 메소드로 연결해준다.
	 * @see net.ib.mtalk.network.NetResultReceiver.Receiver#onNetResult(int, java.lang.Object, java.lang.Object)
	 */
	@Override
	public void onNetResult(int resultCode, Object sentObject, Object receivedObject) {
		try {
			MessageMicro message = ((TcpEnvelope)receivedObject).getMessage();
			if (message instanceof AcquireFileTokenRes) {	// 파일 토큰 요청의 결과일 때
				requireResultReceived((AcquireFileTokenRes) message);
			} else if (message instanceof ValidateUploadRes) {	// 파일 토큰에 대한 확인 결과일 때
				validationResultReceived((ValidateUploadRes) message);
			} else if (message instanceof UploadRes) {	// 각 슬라이스의 업로드 결과일 때
				uploadResultReceived((UploadRes) message);
			}
		} catch(Exception e) {
			MTalkLogWriter.e(e);
			mTransferInfo.setWorking(false);
			// 상태 저장. 에러이므로 반드시.
			updateNonvolatileInfo();
		}
	}

	/**
	 * 네트워크 실패일 때 상태를 저장해야 나중에 재전송을 할지 결정할 수 있다.
	 * @see net.ib.mtalk.network.NetResultReceiver.Receiver#onNetError(int, java.lang.Object)
	 */
	@Override
	public void onNetError(int resultCode, Object sentObject) {
		MTalkLogWriter.e("FileUploadTask.onNetError(): network fail");
		mTransferInfo.setWorking(false);
		// 상태 저장. 에러이므로 반드시.
		updateNonvolatileInfo();
	}

	/* 이하 각 함수의 배치는 위에서부터 차례로 네트워크 쿼리 체인의 순서와 같다. */

	/**
	 * 네트워크 쿼리 체인을 시작한다.
	 */
	protected void startWork() {
		mTransferInfo.setWorking(true);
		// 상태 저장. 유저는 send 버튼을 눌렀다.
		updateNonvolatileInfo();
		if (mTransferInfo.getState() == Database.NONE
				|| mTransferInfo.getState() == Database.RECEIVED_TOKEN) {	// 토큰이 validate 되기 전에는 무조건 처음부터 다시 하자.
			requireFileToken();	// 토큰이 없으면 토큰 요청도 해주자.
		}/* else if (mTransferInfo.getState() == Database.RECEIVED_TOKEN) {
			validateFileToken(mFileToken, mFileSize);	// 이미 토큰이 있으면 그 다음 요청을 시작한다.
		}*/ else if (mTransferInfo.getState() == Database.VALIDATED) {
			uploadEachSlice(mTransferInfo.getSliceNo()+1, mTransferInfo.getNumOfSlice(), mTransferInfo.getSliceSize());
		} else if (mTransferInfo.getState() == Database.SENDING_SLICE) {	// 보내는 중이었는데 재전송 요청이 된 것이다.
			if (mTransferInfo.getSliceNo()+1 < mTransferInfo.getNumOfSlice()) {	// 보낼 게 남았다면
				uploadEachSlice(mTransferInfo.getSliceNo()+1, mTransferInfo.getNumOfSlice(), mTransferInfo.getSliceSize());	// 다음 슬라이스를 보낸다.
			} else {	// 파일 전체가 다 간 거라면
				sendMessage();	// 세션에 메시지를 보냄.
			}
		} else if (mTransferInfo.getState() == Database.SENDING_MESSAGE) {
			// 이미 끝난 작업에 대한 요청이다. 그냥 죽자.
			mTransferInfo.setWorking(false);
		}
	}

	/**
	 * 파일 토큰 요청
	 */
	protected void requireFileToken() {
		// 요청 구성
		AcquireFileTokenReq tokenReq = new AcquireFileTokenReq();
		tokenReq.setFileType(getFileType());
		tokenReq.setFileName(getFile().getName());
		// envelope 구성
		TcpEnvelope tcpEnvelope = new TcpEnvelope.Builder()
		.setMethodId(MethodId.Session.File.AcquireFileToken)
		.setType(TcpEnvelope.REQUEST)
		.setMessage(tokenReq)
		.setCallback(mCallback)
		.build();
		// 발송
		TcpClient.send(tcpEnvelope);
	}

	/**
	 * 파일 토큰 요청의 결과
	 * @param res
	 */
	protected void requireResultReceived(AcquireFileTokenRes res) {
		int responseCode = res.getResponseCode();
		switch (responseCode) {
		case Common.SUCCESS:
			MTalkLogWriter.d("FileUploadTask.validationResultReceived(): require token success");
			mTransferInfo.setFileToken(res.getFileToken());
			// no break
		case Common.FILE_EXISTS:	// 이건 여기선 안 올 것 같지만 일단 달아둔다.
			mTransferInfo.setState(Database.RECEIVED_TOKEN);
			// 상태 저장. 안 해도 됨.
			updateNonvolatileInfo();
			validateFileToken(mTransferInfo.getFileToken(), getFileSize());
			break;
		default:
			MTalkLogWriter.e("FileUploadTask.validationResultReceived(): require token fail. code="+responseCode);
			mTransferInfo.setWorking(false);
			// 상태 저장. 에러이므로 반드시.
			updateNonvolatileInfo();
			ChatDatabaseHelper.updateSendAfter(mContext, mClientMsgUuid, null);
			return;	// 중단
		}
	}

	/**
	 * 파일 토큰의 유효화 요청
	 * @param mFileToken
	 * @param mFileSize
	 */
	protected void validateFileToken(FileToken fileToken, int fileSize) {
		MTalkLogWriter.d("FileUploadTask.validationResultReceived(): token validation req with mFileSize "+fileSize);
		// 요청 구성
		ValidateUploadReq validateUploadReq = new ValidateUploadReq();
		validateUploadReq.setFileToken(fileToken);
		validateUploadReq.setFileSize(fileSize);
		// envelope 구성
		TcpEnvelope tcpEnvelope = new TcpEnvelope.Builder()
		.setMethodId(MethodId.FileServer.File.ValidateUpload)
		.setType(TcpEnvelope.REQUEST)
		.setMessage(validateUploadReq)
		.setCallback(mCallback)
		.build();
		// 발송
		FileClient.send(tcpEnvelope);
	}

	/**
	 * 파일 토큰의 유효화 요청 결과
	 * @param res
	 */
	protected void validationResultReceived(ValidateUploadRes res) {
		int responseCode = res.getResponseCode();
		switch (responseCode) {
		case Common.SUCCESS:
			MTalkLogWriter.d("FileUploadTask.validationResultReceived(): token validation success");
			if (res.hasSliceSize()) {	// 파일 조각 크기를 정의
				mTransferInfo.setSliceSize(res.getSliceSize());
			}
			mTransferInfo.setNumOfSlice(getFileSize()/mTransferInfo.getSliceSize() + (getFileSize()%mTransferInfo.getSliceSize() == 0 ? 0 : 1));
			// no break
		case Common.FILE_EXISTS:
			mTransferInfo.setFileKey(mTransferInfo.getFileToken().getFileKey());	// 키를 추출.
			mTransferInfo.setState(Database.VALIDATED);
			// 상태 저장.
			updateNonvolatileInfo();
			uploadEachSlice(mTransferInfo.getSliceNo()+1, mTransferInfo.getNumOfSlice(), mTransferInfo.getSliceSize());
			break;
		default:
			MTalkLogWriter.e("FileUploadTask.validationResultReceived(): token validation fail. code="+responseCode);
			mTransferInfo.setWorking(false);
			// 상태 저장. 에러이므로 반드시.
			updateNonvolatileInfo();
			return;	// 중단
		}
	}

	/**
	 * 각 슬라이스의 네트워크 쿼리 체인.
	 * @param sliceNo
	 * @param numOfSlice
	 * @param sliceSize
	 * @return
	 */
	protected boolean uploadEachSlice(int sliceNo, int numOfSlice, int sliceSize) {
		try {
			// 파일 키가 없을 경우 디비로부터 읽기
			if ((! mTransferInfo.hasFileKey()) || mTransferInfo.getFileKey() == null) {
				MTalkLogWriter.w("FileUploadTask.uploadEachSlice(): extracting FileKey from DB");
				extractNonvolatileInfo();
				if ((! mTransferInfo.hasFileKey()) || mTransferInfo.getFileKey() == null) {
					// 여기까지 왔는데 디비에도 키가 없다면 말이 안 된다. 이전에 저장을 했었거나 혹은 내가 들고 있었어야 한다. 
					MTalkLogWriter.e("FileUploadTask.uploadEachSlice(): FileKey is not Found");
					// 상태 저장. 에러이므로 반드시.
					updateNonvolatileInfo();
					return false;
				}
			}
			// 최대 현재 슬라이스 길이만큼, 파일 읽기
			RandomAccessFile randomAccessFile = new RandomAccessFile(getFile(), "rw");
			byte[] sliceArray = new byte[sliceSize];	// 현재 처리중인 슬라이스를 담을 임시 버퍼
			randomAccessFile.seek(sliceNo*sliceSize);
			int currentSize = randomAccessFile.read(sliceArray, 0, sliceSize);	// block until return
			if (currentSize < 0) {
				MTalkLogWriter.e("FileUploadTask.uploadEachSlice(): RandomAccessFile seeking over EOF.");
				// 상태 저장. 에러이므로 반드시.
				updateNonvolatileInfo();
				return false;
			}
			randomAccessFile.close();
			// 요청 구성
			UploadReq uploadReq = new UploadReq();
			uploadReq.setFileKey(mTransferInfo.getFileKey());
			uploadReq.setSliceNo(sliceNo);
			uploadReq.setDataSize(currentSize);
			uploadReq.setFileData(ByteStringMicro.copyFrom(sliceArray, 0, currentSize));
			// 메모리 확보
			sliceArray = null;	// 더 이상 사용하지 않으니 즉시 날려준다.
			System.gc();
			// envelope 구성
			TcpEnvelope tcpEnvelope = new TcpEnvelope.Builder()
			.setMethodId(MethodId.FileServer.File.Upload)
			.setType(TcpEnvelope.REQUEST)
			.setMessage(uploadReq)
			.setCallback(mCallback)
			.build();
			// 발송
			FileClient.send(tcpEnvelope);
			MTalkLogWriter.v("FileUploadTask.uploadEachSlice(): (sliceNo: "+sliceNo+", size:"+currentSize+")");
		} catch (Exception e) {
			MTalkLogWriter.e(e);
			mTransferInfo.setWorking(false);
			// 상태 저장. 에러이므로 반드시.
			updateNonvolatileInfo();
			return false;
		}
		return true;
	}

	/**
	 * 각 슬라이스의 네트워크 쿼리 체인의 결과. 이 체인이 끝났는지를 검사해서 다음 쿼리의 실행도 해 줌.
	 * @param res
	 * @param sliceNo
	 * @param numOfSlice
	 * @param sliceSize
	 */
	protected void uploadResultReceived(UploadRes res) {
		int resSliceNo = -1;
		if (res.hasSliceNo()) {
			resSliceNo = res.getSliceNo();
		} else {
			resSliceNo = -1;
		}

		int responseCode = res.getResponseCode();
		switch (responseCode) {
		case Common.SUCCESS:
		case Common.FILE_EXISTS:
			mTransferInfo.setState(Database.SENDING_SLICE);
			// 지금까지 보낸 슬라이스 번호를 추출
			mTransferInfo.setSliceNo(Math.max(mTransferInfo.getSliceNo(), resSliceNo));
			// 상태 저장. 재전송 효율을 따지거나 진행상태를 보여주고자 한다면 있어야 함.
			updateNonvolatileInfo();
			MTalkLogWriter.d("FileUploadTask.uploadResultReceived(): sending "+mTransferInfo.getSliceNo()+"/"+mTransferInfo.getNumOfSlice()+" success");

			// 다음 슬라이스 처리
			if (mTransferInfo.getSliceNo()+1 < mTransferInfo.getNumOfSlice()) {	// 보낼 게 남았다면
				uploadEachSlice(mTransferInfo.getSliceNo()+1, mTransferInfo.getNumOfSlice(), mTransferInfo.getSliceSize());	// 다음 슬라이스를 보낸다.
			} else {	// 파일 전체가 다 간 거라면
				sendMessage();	// 세션에 메시지를 보냄.
			}
			break;
		case Common.UPLOAD_COMPLETE:
			mTransferInfo.setState(Database.SENDING_SLICE);
			mTransferInfo.setSliceNo(resSliceNo);
			// 상태 저장. 재전송 효율을 따지거나 진행상태를 보여주고자 한다면 있어야 함.
			updateNonvolatileInfo();
			MTalkLogWriter.d("FileUploadTask.uploadResultReceived(): sending "+mTransferInfo.getSliceNo()+"/"+mTransferInfo.getNumOfSlice()+" complete");

			sendMessage();	// 세션에 메시지를 보냄.
			return;	// 끝.
		default:
			MTalkLogWriter.e("FileUploadTask.uploadResultReceived(): sending "+resSliceNo+"/"+mTransferInfo.getNumOfSlice()+" fail. code="+responseCode);
			mTransferInfo.setWorking(false);
			// 상태 저장. 에러이므로 반드시.
			updateNonvolatileInfo();
			return;	// 중단
		}
	}

	/**
	 * 파일 업로드가 끝났을 때, 세션 서버에게 메시지 전송을 해야 한다.
	 */
	protected void sendMessage() {
		// 요청 구성
		UserMessage userMessage = new UserMessage();
		userMessage.setContentType(Common.CONTENT_TYPE_HTML);
		userMessage.setContent("");
		userMessage.setSenderUuid(GlobalPreferences.getUserUUID());
		userMessage.setSenderName(GlobalPreferences.getUserName());
		userMessage.setAttachFile(mTransferInfo.getFileKey());
		MTalkLogWriter.v("FileUploadTask.getUserMessage(): Filekey set");

		SendMessageReq sendMessageReq = new SendMessageReq();
		sendMessageReq.setMessage(userMessage);
		sendMessageReq.setViewUuid(InboxDatabaseHelper.getViewUUID(mContext, mClientViewUuid.getMostSignificantBits(), mClientViewUuid.getLeastSignificantBits()));
		// 전송
		SendMessageTask task = new SendMessageTask(mContext, mUiHandler, sendMessageReq,
				mClientViewUuid.getMostSignificantBits(), mClientViewUuid.getLeastSignificantBits(), mClientMsgUuid) {

			/**
			 * 세션 서버에 메시지 전송 후 결과의 처리
			 * @see net.ib.mtalk.network.task.SendMessageTask#onPostExecute(int, java.lang.Object, java.lang.Object)
			 */
			@Override
			protected void onPostExecute(int resultCode, Object requestObj, Object resultObj) {
				MTalkLogWriter.v("FileUploadTask.sendMessage().new SendMessageTask() {...}.onPostExecute() - STT");

				if (resultObj != null) {
					mTransferInfo.setState(Database.SENDING_MESSAGE);
					mTransferInfo.setWorking(false);
					// 상태 저장. 완료되었으므로 반드시.
					updateNonvolatileInfo();

					// 이 함수의 리턴 시점에 이 Task의 작업은 끝난 것임.
					MTalkLogWriter.d("FileUploadTask.sendMessage().new SendMessageTask() {...}.onPostExecute(): File send Success");
				} else {
					mTransferInfo.setWorking(false);
					// 상태 저장. 에러이므로 반드시.
					updateNonvolatileInfo();
				}
				super.onPostExecute(resultCode, requestObj, resultObj);

				MTalkLogWriter.v("FileUploadTask.sendMessage().new SendMessageTask() {...}.onPostExecute() - END");
			}

		};
		MTalkLogWriter.d("FileUploadTask.sendMessage(): file message to session server");
		task.execute();
	}


}